/////////////////////////////////////////////////////////////////////////////
// This file is part of the "Java-DAP" project, a Java implementation
// of the OPeNDAP Data Access Protocol.
//
// Copyright (c) 2007 OPeNDAP, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
/////////////////////////////////////////////////////////////////////////////
using System;
using System.Text;
using NDODS.Util;

namespace NDODS.DAP.Utils
{
    /// <summary> User: ndp
    /// Date: Jul 7, 2006
    /// Time: 10:23:19 AM
    /// </summary>
    public class EscapeStrings
    {
        /// <summary> This method is used to normalize strings prior
        /// to their inclusion in XML documents. XML has certain parsing requirements
        /// around reserved characters. These reserved characters must be replaced with
        /// symbols recognized by the XML parser as place holder for the actual symbol.
        /// <p/>
        /// The rule for this normalization is as follows:
        /// <p/>
        /// <ul>
        /// <li> The &lt; (less than) character is replaced with &amp;lt;
        /// <li> The &gt; (greater than) character is replaced with &amp;gt;
        /// <li> The &amp; (ampersand) character is replaced with &amp;amp;
        /// <li> The ' (apostrophe) character is replaced with &amp;apos;
        /// <li> The &quot; (double quote) character is replaced with &amp;quot;
        /// </ul>
        /// 
        /// </summary>
        /// <param name="s">The String to be normalized.
        /// </param>
        /// <returns> The normalized String.
        /// </returns>
        public static String normalizeToXML(String s)
        {
            // Some handy definitons.
            String xmlGT = "&gt;";
            String xmlLT = "&lt;";
            String xmlAmp = "&amp;";
            String xmlApos = "&apos;";
            String xmlQuote = "&quot;";

            StringBuilder sb = new StringBuilder(s);

            for (int offset = 0; offset < sb.Length; offset++)
            {
                char c = sb[offset];

                switch (c)
                {
                    case '>': // GreaterThan
                        sb.Replace(sb.ToString(offset, offset + 1 - offset), xmlGT, offset, offset + 1 - offset);
                        break;


                    case '<': // Less Than
                        sb.Replace(sb.ToString(offset, offset + 1 - offset), xmlLT, offset, offset + 1 - offset);
                        break;


                    case '&': // Ampersand
                        sb.Replace(sb.ToString(offset, offset + 1 - offset), xmlAmp, offset, offset + 1 - offset);
                        break;


                    case '\'': // Single Quote
                        sb.Replace(sb.ToString(offset, offset + 1 - offset), xmlApos, offset, offset + 1 - offset);
                        break;


                    case '\"': // Double Quote
                        sb.Replace(sb.ToString(offset, offset + 1 - offset), xmlQuote, offset, offset + 1 - offset);
                        break;


                    default:
                        break;
                }
            }

            if (Debug.isSet("EscapeStrings.normalizeToXML"))
                Console.Out.WriteLine("String: `" + s + "` normalized to: `" + sb + "`");

            return (sb.ToString());
        }


        // May need to include/exclude the escape character!
        private static char[] _allowableInURI =
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-+_/.\\*".ToCharArray();

        private static char[] _allowableInURI_CE =
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-+_/.\\,".ToCharArray();

        private static char _URIEscape = '%';

        /// <summary> Replace characters that are not allowed in WWW URLs using rules specific
        /// to Constraint Expressions. This has canged over time and now the only
        /// differences are:
        /// <ui>
        /// <li>'*' is escaped by this function while it is not
        /// escaped by id2www().</li>
        /// <li> ',' is not escaped by this function and it is by id2www</li>
        /// </ui>
        /// The set of characters that are allowed in a CE are:
        /// "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-+_/.\";
        /// All other characters will replaced with their hexidecimal value preceeded by
        /// the "%" character. Thus a space, " ", character will be represented in the
        /// returned string as "%20".
        /// 
        /// </summary>
        /// <param name="in_Renamed">The string in which to replace characters.
        /// </param>
        /// <returns> The modified identifier.
        /// </returns>
        public static String id2www_ce(String in_Renamed)
        {
            String s;

            try
            {
                s = escapeString(in_Renamed, _allowableInURI_CE, _URIEscape);
            }
            catch (Exception )
            {
                s = null;
            }
            return s;
        }


        /// <summary> Replace all characters in the String <code>in</code> not present in the String <code>allowable</code> with
        /// their hexidecimal values (encoded as ASCII) and preceeded by the String <code>esc</code>
        /// <p/>
        /// The <code>esc</code> character may not appear on the allowable list, as if it did it would break the 1:1
        /// and onto mapping between the unescaped character space and the escaped characater space.
        /// 
        /// </summary>
        /// <param name="in_Renamed">       The string in which to replace characters.
        /// </param>
        /// <param name="allowable">The set of allowable characters.
        /// </param>
        /// <param name="esc">      The escape String (typically "%" for a URI or "\" for a regular expression).
        /// </param>
        /// <returns> The modified identifier.
        /// </returns>
        public static String escapeString(String in_Renamed, char[] allowable, char esc)
        {
            String out_Renamed = "";


            if (in_Renamed == null)
                return null;

            if (isEscAllowed(allowable, esc))
            {
                throw new Exception("Escape character MAY NOT be in the list of allowed characters!");
            }


            char[] inca = in_Renamed.ToCharArray();

            //UPGRADE_ISSUE: The following fragment of code could not be parsed and was not converted. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1156'"
            foreach (char candidate in inca)
            {
                bool isAllowed;
                isAllowed = false;
                //UPGRADE_ISSUE: The following fragment of code could not be parsed and was not converted. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1156'"
                foreach (char allowed in allowable)
                {
                    if (candidate == allowed)
                        isAllowed = true;
                }
                if (isAllowed)
                {
                    if (Debug.isSet("EscapeStrings.escapeString"))
                        Console.Out.WriteLine("candidate: " + candidate + "(" + ((int) candidate) +
                                              ")     Integer.toHexString(): " + Convert.ToByte(candidate).ToString("X"));
                    out_Renamed += candidate;
                }
                else
                {
                    String c;
                    c = Convert.ToByte(candidate).ToString("X");
                    if (Debug.isSet("EscapeStrings.escapeString"))
                        Console.Out.WriteLine("candidate: " + candidate + "(" + ((int) candidate) +
                                              ")     Integer.toHexString(): " + c);
                    if (c.Length < 2)
                        c = "0" + c;
                    out_Renamed += (esc + c);
                }
            }

            return out_Renamed;
        }

        private static bool isEscAllowed(char[] allowable, char esc)
        {
            //UPGRADE_ISSUE: The following fragment of code could not be parsed and was not converted. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1156'"
            foreach (char allowed in allowable)
            {
                if (esc == allowed)
                    return true;
            }
            return false;
        }

        /// <summary> Replace characters that are not allowed in DAP2 identifiers.
        /// The set of characters that are allowed in a URI are:
        /// "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-+_/.\*";
        /// All other characters will replaced with their hexidecimal value preceeded by
        /// the "%" character. Thus a space, " ", character will be represented in the
        /// returned string as "%20".
        /// 
        /// </summary>
        /// <param name="in_Renamed">The string in which to replace characters.
        /// </param>
        /// <returns> The modified identifier.
        /// </returns>
        public static String id2www(String in_Renamed)
        {
            String s;

            try
            {
                s = escapeString(in_Renamed, _allowableInURI, _URIEscape);
            }
            catch (Exception )
            {
                s = null;
            }
            return s;
        }


        /// <summary> Given a string that contains WWW escape sequences, translate those escape
        /// sequences back into ASCII characters. Return the modified string.
        /// 
        /// </summary>
        /// <param name="in_Renamed">    The string to modify.
        /// </param>
        /// <param name="escape">The character used to signal the begining of an escape sequence.
        /// </param>
        /// <param name="except">If there is some escape code that should not be removed by
        /// this call (e.g., you might not want to remove spaces, %20) use this
        /// parameter to specify that code. The function will then transform all
        /// escapes except that one.
        /// </param>
        /// <returns> The modified string.
        /// </returns>
        public static String unescapeString(String in_Renamed, char escape, String except)
        {
            if (Debug.isSet("EscapeStrings.unescapeString"))
                Console.Out.WriteLine("string: \"" + in_Renamed + "\" length: " + in_Renamed.Length);

            if (in_Renamed == null)
                return null;

            String esc = Convert.ToString(escape);
            String out_Renamed = in_Renamed;
            int i = 0;
            //UPGRADE_WARNING: Method 'java.lang.String.indexOf' was converted to 'System.String.IndexOf' which may throw an exception. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1101'"
            while ((i = out_Renamed.IndexOf(esc, i)) != - 1)
            {
                String candidate = out_Renamed.Substring(i, (i + 3) - (i));

                if (Debug.isSet("EscapeStrings.unescapeString"))
                    Console.Out.WriteLine("index: " + i + "  candidate: " + candidate);

                if (candidate.Equals(except))
                {
                    if (Debug.isSet("EscapeStrings.unescapeString"))
                        Console.Out.WriteLine("candiate equals excepted string, not replaced");
                    i += 3;
                }
                else
                {
                    //out = out.substring(0,i) + " + [esc]" + out.substring(i+1,i+3) + " + " + out.substring(i+3,out.length());

                    String replacement;
                    replacement =
                        Convert.ToString((char) Convert.ToInt32(out_Renamed.Substring(i + 1, (i + 3) - (i + 1)), 16));

                    out_Renamed = out_Renamed.Substring(0, (i) - (0)) + replacement +
                                  out_Renamed.Substring(i + 3, (out_Renamed.Length) - (i + 3));

                    if (replacement.Equals(esc))
                        i++;

                    if (Debug.isSet("EscapeStrings.unescapeString"))
                        Console.Out.WriteLine("replacement: " + replacement);
                }
            }

            return out_Renamed;
        }


        /// <summary> Given a string that contains WWW escape sequences, translate those escape
        /// sequences back into ASCII characters. Escape sequences are indicted by a
        /// leading "%" character followed by 2 characters indicating the hexidecimal
        /// value of the character that was escaped.
        /// 
        /// </summary>
        /// <param name="in_Renamed">The string to modify.
        /// </param>
        /// <returns> The modified string.
        /// </returns>
        public static String www2id(String in_Renamed)
        {
            return unescapeString(in_Renamed, _URIEscape, "");
        }


        /// <summary> Given a string that contains WWW escape sequences, translate those escape
        /// sequences back into ASCII characters, with the exception of the escaped
        /// space (0x20) character which appears as "%20". THe Constraint Expression
        /// Parser will break if there are spaces in the CE. Escape sequences are
        /// indicted by a leading "%" character followed by 2 characters indicating
        /// the hexidecimal value of the character that was escaped.
        /// 
        /// </summary>
        /// <param name="in_Renamed">The string to modify.
        /// </param>
        /// <returns> The modified string.
        /// </returns>
        public static String www2ce(String in_Renamed)
        {
            return unescapeString(in_Renamed, _URIEscape, "%20");
        }


        [STAThread]
        public static void Main(String[] args)
        {
            Debug.set_Renamed("EscapeStrings.escapeString", false);
            Debug.set_Renamed("EscapeStrings.unescapeString", false);

            if (args.Length > 0)
            {
                //UPGRADE_ISSUE: The following fragment of code could not be parsed and was not converted. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1156'"
                foreach (String s in args)
                {
                    Console.Out.WriteLine("id2www - Input: \"" + s + "\"   Output: \"" + id2www(s) + "\"   recaptured: " +
                                          www2id(id2www(s)));
                }
                //UPGRADE_ISSUE: The following fragment of code could not be parsed and was not converted. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1156'"
                foreach (String s in args)
                {
                    String out_Renamed = id2www(s);
                    Console.Out.WriteLine("www2id - Input: \"" + out_Renamed + "\"   Output: \"" + www2id(out_Renamed) +
                                          "\" recaptured: " + id2www(www2id(out_Renamed)));
                }
            }
            else
            {
                char[] allBytes = new char[256];

                for (int b = 0; b < 256; b++)
                    allBytes[b] = (char) b;
                String allChars = new String(allBytes);
                Console.Out.WriteLine("id2www All Characters");
                Console.Out.WriteLine("Input String:      \"" + allChars + "\"");
                Console.Out.WriteLine("Output String:     \"" + id2www(allChars) + "\"");
                Console.Out.WriteLine("Recaptured String: \"" + www2id(id2www(allChars)) + "\" ");
            }
        }
    }
}